package com.mengxuegu.workflow;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ActivitiTest02Model {

    @Autowired
    ProcessEngine processEngine;

    @Autowired
    RepositoryService repositoryService;

    @Autowired
    ObjectMapper objectMapper;

    /**
     * 查询所有流程模型 ACT_RE_MODEL
     */
    @Test
    public void modelList() {
        // 获取模型查询对象
        ModelQuery query = repositoryService.createModelQuery();
        // 按模型创建时间 降序 排列
        List<Model> list = query.orderByCreateTime().desc().list();
        for (Model model : list) {
            System.out.print("模型id：" + model.getId());
            System.out.print("，模型名称：" + model.getName());
            System.out.print("，模型描述：" + model.getMetaInfo());
            System.out.print("，模型标识key：" + model.getKey());
            System.out.print("，模型版本号：" + model.getVersion());
            System.out.print("，创建时间：" + model.getCreateTime());
            System.out.println("，更新时间：" + model.getLastUpdateTime());
        }
    }

    /*** 删除模型： * 涉及表：ACT_RE_MODEL、ACT_GE_BYTEARRAY */
    @Test
    public void deleteModel() {
        // 模型id
        String id = "d992c33a-396c-11ef-9877-5e9274e72630";
        repositoryService.deleteModel(id);
        System.out.println("删除成功");
    }

    /*** 导出下载模型图zip压缩包(.bpmn20.xml流程描述和.png图片资源) */
    @Test
    public void exportZip() throws Exception {
        // 模型id
        String id = "b95f0452-397a-11ef-8847-5e9274e72630";
        // 查询模型信息
        Model model = repositoryService.getModel(id);
        if (model != null) {
            // 获取流程图 json 字节码
            byte[] bpmnJsonBytes = repositoryService.getModelEditorSource(id);
            // 流程图 json 字节码转 xml 字节码
            byte[] xmlBytes = bpmnJsonToXmlBytes(bpmnJsonBytes);
            if (xmlBytes == null) {
                System.out.println("模型数据为空-请先设计完整流程-再导出");
            } else {
                // 压缩包文件名
                String zipName = model.getName() + "." + model.getKey() + ".zip";
                // 文件输出流
                File file = new File("D:/" + zipName);
                FileOutputStream outputStream = new FileOutputStream(file);
                // 实例化zip压缩对象输出流
                ZipOutputStream zipos = new ZipOutputStream(outputStream);
                // 指定压缩包里的 name.bpmn20.xml 文件名
                zipos.putNextEntry(new ZipEntry(model.getName() + ".bpmn20.xml"));
                // 将xml写入压缩流
                zipos.write(xmlBytes);
                // 查询png图片，
                byte[] pngBytes = repositoryService.getModelEditorSourceExtra(id);
                if (pngBytes != null) {
                    // 指定压缩包里的 name.key.png 文件名
                    zipos.putNextEntry(new ZipEntry(model.getName() + "." + model.getKey() + ".png"));
                    // 图片文件写到压缩包中
                    zipos.write(pngBytes);
                }
                zipos.closeEntry();
                zipos.close();
                System.out.println("导出成功");
            }
        } else {
            System.out.println("模型不存在");
        }
    }

    /**
     * 流程图保存的时候是json串，引擎认识的却是符合bpmn2.0规范的xml，需要将 json 字节码转 xml 字节码
     */
    private byte[] bpmnJsonToXmlBytes(byte[] jsonBytes) throws IOException {
        if (jsonBytes == null) {
            return null;
        }
        // json转回BpmnModel对象
        JsonNode modelNode = objectMapper.readTree(jsonBytes);
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);
        if (bpmnModel.getProcesses().size() == 0) {
            return null;
        }
        // BpmnModel对象转xml字节数组
        return new BpmnXMLConverter().convertToXML(bpmnModel);
    }

    /*** 导出下载模型 xml 文件 */
    @Test
    public void exportXml() throws Exception {
        // 模型id
        String id = "1c3b7d73-c833-11eb-98fd-ee5b4f08d062";
        ByteArrayInputStream in = null;
        // 获取流程图 json 字节码
        byte[] bytes = repositoryService.getModelEditorSource(id);
        // json转xml字节数组
        String filename = null;
        if (bytes != null) {
            JsonNode modelNode = objectMapper.readTree(bytes);
            BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            if (bpmnModel.getProcesses().size() != 0) {
                // 转xml字节数组
                byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
                in = new ByteArrayInputStream(bpmnBytes);
                // 如果流程名称为空，则取流程定义key
                filename = StringUtils.isEmpty(bpmnModel.getMainProcess().getName()) ? bpmnModel.getMainProcess().getId() : bpmnModel.getMainProcess().getName();
            }
        }
        if (filename == null) {
            filename = "模型数据为空，请先设计流程，再导出";
            in = new ByteArrayInputStream(filename.getBytes("UTF-8"));
        }
        // 文件输出流
        FileOutputStream out = new FileOutputStream(new File("D:/" + filename + ".bpmn20.xml"));
        // 输入流，输出流的转换
        IOUtils.copy(in, out);
        // 关闭流
        out.close();
        in.close();
        System.out.println("下载模型 xml 文件成功");
    }

    /**
     * 通过流程模型 进行 流程定义部署
     *
     * 流程图保存的时候是json串，引擎认识的却是符合bpmn2.0规范的xml，
     * 所以在首次的部署的时候要将json串转换为BpmnModel，
     * 再将BpmnModel转换成xml保存进数据库，以后每次使用就直接将xml转换成BpmnModel。
     *
     * 实际项目中如果不用activiti自带的设计器，可以考虑用插件，直接生成的是 xml，
     * 或者自己开发设计器，在后端生成节点及其属性，引擎有现成的节点实体，如：开始节点StartEvent，线SequenceFlow等。
     *
     * 涉及表：
     * ACT_RE_PROCDEF 新增数据: 流程定义数据
     * ACT_RE_DEPLOYMENT 新增数据: 流程部署数据
     * ACT_GE_BYTEARRAY 新增数据：将当前流程图绑定到此流程定义部署数据上
     * ACT_RE_MODEL 更新部署id
     *
     * 每个流程定义模型可以多次流程定义部署，activiti 通过流程定义模型中的标识 key 来判断是否为同一流程模型，
     * 相同标识key则视为同一流程定义模型。
     *
     * 相同的标识key流程定义模型，每部署一次对应的新增一条流程定义数据，对应流程定义版本号会基于之前的加1。
     */
    @Test
    public void deploy() throws Exception {
        // 模型id
        String id = "b95f0452-397a-11ef-8847-5e9274e72630";
        // 获取流程图 json 字节码
        byte[] jsonBytes = repositoryService.getModelEditorSource(id);
        if (jsonBytes == null) {
            System.out.println("模型数据为空，请先设计流程并成功保存，再进行发布。");
            return;
        }
        // 转xml字节数组
        byte[] xmlBytes = bpmnJsonToXmlBytes(jsonBytes);
        if (xmlBytes == null) {
            System.out.println("数据模型不符要求，请至少设计一条主线流程。");
            return;
        }
        // 流程图片字节码
        byte[] pngBytes = repositoryService.getModelEditorSourceExtra(id);
        // 获取模型
        Model model = repositoryService.getModel(id);
        // 流程定义xml名称
        String processName = model.getName() + ".bpmn20.xml";
        // 流程定义png名称
        String pngName = model.getName() + "." + model.getKey() + ".png";
        // 流程部署
        Deployment deployment = repositoryService.createDeployment()
                .name(model.getName())
                .addString(processName, new String(xmlBytes, "UTF-8"))  // xml文件
                .addBytes(pngName, pngBytes)  // 图片
                .deploy();
        // 更新 部署id 到模型对象（将模型与部署数据绑定）
        model.setDeploymentId(deployment.getId());
        repositoryService.saveModel(model);
        System.out.println("部署完成");
    }

}
